// File [test/stdx/unique_ptr/01/main.cpp].

/*
    (c) Copyright Alf P. Steinbach 2008.
    Testing std::unique_ptr

    This is Howard Hinnant's public domain unit test of his unique_ptr emulation, at
    <url: http://home.twcny.rr.com/hinnant/cpp_extensions/unique_ptr_test.html>.

    Changes: namespace alfs::hh for unique_ptr.

    Expected: compiles cleanly, with

    <output>
        Pass
    </output>
*/

#include <alfs/bundled_lib/unique_ptr.hpp>
#include <alfs/stdx/static_assert.hpp>      // static_assert,
#include <alfs/stdx/type_traits.hpp>        // is_same etc., alfs::is_defined_has_virtual_destructor
#include <cassert>
#include <iostream>

struct do_nothing
{
    template <class T>
    void operator()(T*) {}

    template <class T>
    void operator()(T*, std::size_t) {}
};

struct stateful
{
    template <class T>
    void operator()(T*) {}

    template <class T>
    void operator()(T*, std::size_t) {}

    explicit stateful(int data = -1) : data_(data) {}
    int data_;
};

template <class T>
struct pointer
{
    T* p_;
    pointer() : p_(0) {}
    template <class U> pointer(const pointer<U>& u) : p_(u.p_) {}
    operator bool() const {return !!p_;}

    friend bool operator==(pointer x, pointer y) {return x.p_ == y.p_;}
    friend bool operator!=(pointer x, pointer y) {return x.p_ != y.p_;}
};

template <class T>
struct special_pointer
{
    typedef ::pointer<T> pointer;

    special_pointer() {}
    template <class U> special_pointer(const special_pointer<U>&) {}
    void operator()(pointer p) {delete p.p_;}
};

template <class T>
struct special_pointer<T[]>
{
    typedef ::pointer<T> pointer;

    void operator()(pointer p) {delete [] p.p_;}
    void operator()(pointer p, std::size_t) {delete [] p.p_;}
};

template <class T, std::size_t N>
struct special_pointer<T[N]>
{
    typedef ::pointer<T> pointer;

    void operator()(pointer p) {delete [] p.p_;}
    void operator()(pointer p, std::size_t) {delete [] p.p_;}
};

void test_nested_types()
{
    using namespace std;
    using namespace alfs::hh;

    static_assert((std::is_same<int, unique_ptr<int>::element_type>::value), "");
    static_assert((is_same<int, unique_ptr<int[]>::element_type>::value), "");
    static_assert((is_same<int, unique_ptr<int[3]>::element_type>::value), "");

    static_assert((is_same<default_delete<int>, unique_ptr<int>::deleter_type>::value), "");
    static_assert((is_same<default_delete<int[]>, unique_ptr<int[]>::deleter_type>::value), "");
    static_assert((is_same<default_delete<int[3]>, unique_ptr<int[3]>::deleter_type>::value), "");

    static_assert((is_same<int*, unique_ptr<int>::pointer>::value), "");
    static_assert((is_same<int*, unique_ptr<int[]>::pointer>::value), "");
    static_assert((is_same<int*, unique_ptr<int[3]>::pointer>::value), "");

    static_assert((is_same<int, unique_ptr<int, special_pointer<int> >::element_type>::value), "");
    static_assert((is_same<int, unique_ptr<int[], special_pointer<int[]> >::element_type>::value), "");
    static_assert((is_same<int, unique_ptr<int[3], special_pointer<int[3]> >::element_type>::value), "");

    static_assert((is_same<special_pointer<int>, unique_ptr<int, special_pointer<int> >::deleter_type>::value), "");
    static_assert((is_same<special_pointer<int[]>, unique_ptr<int[], special_pointer<int[]> >::deleter_type>::value), "");
    static_assert((is_same<special_pointer<int[3]>, unique_ptr<int[3], special_pointer<int[3]> >::deleter_type>::value), "");

    static_assert((is_same<special_pointer<int>::pointer, unique_ptr<int, special_pointer<int> >::pointer>::value), "");
    static_assert((is_same<special_pointer<int[]>::pointer, unique_ptr<int[], special_pointer<int[]> >::pointer>::value), "");
    static_assert((is_same<special_pointer<int[3]>::pointer, unique_ptr<int[3], special_pointer<int[3]> >::pointer>::value), "");

    static_assert((sizeof(unique_ptr<int>) == sizeof(int*)), "");
    static_assert((sizeof(unique_ptr<int[]>) == sizeof(int*)), "");
    static_assert((sizeof(unique_ptr<int[3]>) == sizeof(int*)), "");

    static_assert((sizeof(unique_ptr<int, special_pointer<int> >) == sizeof(int*)), "");
    static_assert((sizeof(unique_ptr<int[], special_pointer<int[]> >) == sizeof(int*)), "");
    static_assert((sizeof(unique_ptr<int[3], special_pointer<int[3]> >) == sizeof(int*)), "");
}

void test_default_ctor()
{
    using namespace std;
    using namespace std::tr1;
    using namespace alfs::hh;
    {
    unique_ptr<int> p1;
    assert(p1.get() == 0);

    unique_ptr<int[]> p2;
    assert(p2.get() == 0);

    unique_ptr<int[3]> p3;
    assert(p3.get() == 0);
    }
    {
    unique_ptr<int, stateful> p1;
    assert(p1.get() == 0);
    assert(p1.get_deleter().data_ == -1);

    unique_ptr<int[], stateful> p2;
    assert(p2.get() == 0);
    assert(p2.get_deleter().data_ == -1);

    unique_ptr<int[3], stateful> p3;
    assert(p3.get() == 0);
    assert(p3.get_deleter().data_ == -1);
    }
}

struct base
{
    virtual ~base() {}
};

struct derived : base {};

void test_ptr_ctor()
{
    using namespace std;
    using namespace std::tr1;
    using namespace alfs::hh;
    {
    base* bp = new base;
    unique_ptr<base> p1(bp);
    assert(p1.get() == bp);

    derived* dp = new derived;
    unique_ptr<base> p2(dp);
    assert(p2.get() == dp);

    base* bpa = new base[3];
    unique_ptr<base[]> p3(bpa);
    assert(p3.get() == bpa);

    base* bpa3 = new base[3];
    unique_ptr<base[3]> p4(bpa3);
    assert(p4.get() == bpa3);

//  unique_ptr<base[]> p5(new derived[3]);   // should not compile
//  unique_ptr<base[3]> p6(new derived[3]);  // should not compile
    }
    {
    special_pointer<base>::pointer bp;
    bp.p_ = new base;
    unique_ptr<base, special_pointer<base> > sp1(bp);//OOPS
    //assert(sp1.get().p_ == bp.p_);//From OOPS

    special_pointer<derived>::pointer dp;
    dp.p_ = new derived;
    unique_ptr<base, special_pointer<base> > sp2(dp);
    assert(sp2.get().p_ == dp.p_);

    special_pointer<base>::pointer bpa;
    bpa.p_ = new base[3];
    unique_ptr<base[], special_pointer<base[]> > sp3(bpa);
    assert(sp3.get().p_ == bpa.p_);

    special_pointer<base>::pointer bpa3;
    bpa3.p_ = new base[3];
    unique_ptr<base[3], special_pointer<base[3]> > sp4(bpa3);
    assert(sp4.get().p_ == bpa3.p_);

//  unique_ptr<base[], special_pointer<base[]> > sp5(special_pointer<derived[]>::pointer());   // should not compile
//  unique_ptr<base[3], special_pointer<base[3]> > sp6(special_pointer<derived[3]>::pointer());   // should not compile
    }
}

void test_ptr_deleter_ctor()
{
    using namespace std;
    using namespace alfs::hh;
    using namespace std::tr1;
    {
    stateful d1(1);
    base bp1;
    unique_ptr<base, stateful> p1(&bp1, d1);
    assert(p1.get() == &bp1);
    assert(p1.get_deleter().data_ == d1.data_);
    d1.data_ += 1;
    assert(p1.get_deleter().data_ != d1.data_);
    }
    {
    base bp1;
    unique_ptr<base, stateful> p1(&bp1, stateful(1));
    assert(p1.get() == &bp1);
    assert(p1.get_deleter().data_ == 1);
    }
    {
    base bp1;
//  unique_ptr<base, const stateful&> p1(&bp1, stateful(1));  // should not compile
    }
    {
    stateful d1(1);
    base bp1;
    unique_ptr<base, stateful&> p1(&bp1, d1);
    assert(p1.get() == &bp1);
    assert(&p1.get_deleter() == &d1);
    d1.data_ += 1;
    assert(p1.get_deleter().data_ == d1.data_);
    }
    {
    stateful d1(1);
    derived dp1;
    unique_ptr<base, stateful> p1(&dp1, d1);
    assert(p1.get() == &dp1);
    assert(p1.get_deleter().data_ == d1.data_);
    d1.data_ += 1;
    assert(p1.get_deleter().data_ != d1.data_);
    }
    {
    special_pointer<base> d1;
    special_pointer<base>::pointer bp1;
    bp1.p_ = new base;
    unique_ptr<base, special_pointer<base>&> p1(bp1, d1);
    assert(p1.get().p_ == bp1.p_);
    assert(&p1.get_deleter() == &d1);
    }

    {
    stateful d1(1);
    base bp1;
    unique_ptr<base[], stateful> p1(&bp1, d1);
    assert(p1.get() == &bp1);
    assert(p1.get_deleter().data_ == d1.data_);
    d1.data_ += 1;
    assert(p1.get_deleter().data_ != d1.data_);
    }
    {
    base bp1;
    unique_ptr<base[], stateful> p1(&bp1, stateful(1));
    assert(p1.get() == &bp1);
    assert(p1.get_deleter().data_ == 1);
    }
    {
    base bp1;
//  unique_ptr<base[], const stateful&> p1(&bp1, stateful(1));  // should not compile
    }
    {
    stateful d1(1);
    base bp1;
    unique_ptr<base[], stateful&> p1(&bp1, d1);
    assert(p1.get() == &bp1);
    assert(&p1.get_deleter() == &d1);
    d1.data_ += 1;
    assert(p1.get_deleter().data_ == d1.data_);
    }
    {
    stateful d1(1);
    derived dp1;
//  unique_ptr<base[], stateful> p1(&dp1, d1);  // should not compile
    }
    {
    special_pointer<base[]> d1;
    special_pointer<base[]>::pointer bp1;
    bp1.p_ = new base[3];
    unique_ptr<base[], special_pointer<base[]>&> p1(bp1, d1);
    assert(p1.get().p_ == bp1.p_);
    assert(&p1.get_deleter() == &d1);
    }

    {
    stateful d1(1);
    base bp1;
    unique_ptr<base[3], stateful> p1(&bp1, d1);
    assert(p1.get() == &bp1);
    assert(p1.get_deleter().data_ == d1.data_);
    d1.data_ += 1;
    assert(p1.get_deleter().data_ != d1.data_);
    }
    {
    base bp1;
    unique_ptr<base[3], stateful> p1(&bp1, stateful(1));
    assert(p1.get() == &bp1);
    assert(p1.get_deleter().data_ == 1);
    }
    {
    base bp1;
//  unique_ptr<base[3], const stateful&> p1(&bp1, stateful(1));  // should not compile
    }
    {
    stateful d1(1);
    base bp1;
    unique_ptr<base[3], stateful&> p1(&bp1, d1);
    assert(p1.get() == &bp1);
    assert(&p1.get_deleter() == &d1);
    d1.data_ += 1;
    assert(p1.get_deleter().data_ == d1.data_);
    }
    {
    stateful d1(1);
    derived dp1;
//  unique_ptr<base[3], stateful> p1(&dp1, d1);  // should not compile
    }
    {
    special_pointer<base[3]> d1;
    special_pointer<base[3]>::pointer bp1;
    bp1.p_ = new base[3];
    unique_ptr<base[3], special_pointer<base[3]>&> p1(bp1, d1);
    assert(p1.get().p_ == bp1.p_);
    assert(&p1.get_deleter() == &d1);
    }
}

void test_copy_ctor()
{
    using namespace std;
    using namespace std::tr1;
    using namespace alfs::hh;
    {
    //static_assert( alfs::HasVirtualDestructor<base>::value != alfs::no, "" );       // APS
    //static_assert( has_virtual_destructor<base>::value, "" );           // APS.

    unique_ptr<derived> p1;
//  unique_ptr<derived> p2(p1);  // should not compile
    unique_ptr<derived> p3(move(p1));
//  unique_ptr<base> p4(p3);  // should not compile
    unique_ptr<base> p5(move(p3));
    }
    {
    unique_ptr<derived, special_pointer<derived> > p1;
//  unique_ptr<derived, special_pointer<derived> > p2(p1);  // should not compile
    unique_ptr<derived, special_pointer<derived> > p3(move(p1));
//  unique_ptr<base, special_pointer<base> > p4(p3);  // should not compile
    unique_ptr<base, special_pointer<base> > p5(move(p3));
    }

    {
    unique_ptr<derived[]> p1;
//  unique_ptr<derived[]> p2(p1);  // should not compile
    unique_ptr<derived[]> p3(move(p1));
//  unique_ptr<base[]> p4(p3);  // should not compile
//  unique_ptr<base[]> p5(move(p3));  // should not compile
    }
    {
    unique_ptr<derived[], special_pointer<derived[]> > p1;
//  unique_ptr<derived[], special_pointer<derived[]> > p2(p1);  // should not compile
    unique_ptr<derived[], special_pointer<derived[]> > p3(move(p1));
//  unique_ptr<base[], special_pointer<base[]> > p4(p3);  // should not compile
//  unique_ptr<base[], special_pointer<base[]> > p5(move(p3));  // should not compile
    }

    {
    unique_ptr<derived[3]> p1;
//  unique_ptr<derived[3]> p2(p1);  // should not compile
    unique_ptr<derived[3]> p3(move(p1));
//  unique_ptr<base[3]> p4(p3);  // should not compile
//  unique_ptr<base[3]> p5(move(p3));  // should not compile
    }
    {
    unique_ptr<derived[3], special_pointer<derived[3]> > p1;
//  unique_ptr<derived[3], special_pointer<derived[3]> > p2(p1);  // should not compile
    unique_ptr<derived[3], special_pointer<derived[3]> > p3(move(p1));
//  unique_ptr<base[3], special_pointer<base[3]> > p4(p3);  // should not compile
//  unique_ptr<base[3], special_pointer<base[3]> > p5(move(p3));  // should not compile
    }
}

void test_assignment()
{
    using namespace std;
    using namespace std::tr1;
    using namespace alfs::hh;
    {
    unique_ptr<derived> p1(new derived);
    unique_ptr<derived> p2(new derived);
//  p2 = p1;  // should not compile
    p2 = move(p1);
    unique_ptr<base> p3(new base);
//  p3 = p2;  // should not compile
    p3 = move(p2);
    }
    {
    unique_ptr<derived, special_pointer<derived> > p1;
    unique_ptr<derived, special_pointer<derived> > p2;
//  p2 = p1;  // should not compile
    //p2 = move(p1);//OOPS
    unique_ptr<base, special_pointer<base> > p3;
//  p3 = p2;  // should not compile
    p3 = move(p2);
    }

    {
    //unique_ptr<derived[]> p1(new derived[3]);//OOPS
    unique_ptr<derived[]> p2(new derived[3]);
//  p2 = p1;  // should not compile
    //p2 = move(p1);//From OOPS
    unique_ptr<base[]> p3(new base[3]);
//  p3 = p2;  // should not compile
//  p3 = move(p2);  // should not compile
    }
    {
    unique_ptr<derived[], special_pointer<derived[]> > p1;
    unique_ptr<derived[], special_pointer<derived[]> > p2;
//  p2 = p1;  // should not compile
    p2 = move(p1);
    unique_ptr<base[], special_pointer<base[]> > p3;
//  p3 = p2;  // should not compile
//  p3 = move(p2);  // should not compile
    }
}

//template< typename T > struct HasVirtualDestructor
//    {
//        enum {
//#ifdef BOOST_HAS_VIRTUAL_DESTRUCTOR
//    value = Bool3EnumFromBool<BOOST_HAS_VIRTUAL_DESTRUCTOR(T)>::value
//#else
//    value = alfs::maybe
//#endif
//        };
//    };

namespace alfs {

    //template< typename T > struct HasVirtualDestructor
    //{
    //    enum {
    //        value = alfs::Bool3::maybe
    //    };
    //};
}  // alfs

int main()
{
    test_nested_types();
    test_default_ctor();
    test_ptr_ctor();
    test_ptr_deleter_ctor();
    test_copy_ctor();
    test_assignment();

    std::cout << "Pass" << std::endl;
    std::cout << alfs::HasVirtualDestructor<base>::value << std::endl;
    enum{ x = alfs::HasVirtualDestructor<base>::value };
    //char c[x];
}
